/*
 * ========================================
 * 物理内存分配器 (Physical Memory Allocator)
 * ========================================
 * 
 * 这个文件实现了简单的物理内存分配器，用于管理内核的物理内存。
 * 它提供了 kalloc() 和 kfree() 函数来分配和释放物理页面。
 * 
 * 设计思路：
 * 1. 使用简单的链表来管理空闲页面
 * 2. 每个空闲页面都包含一个指向下一个空闲页面的指针
 * 3. 分配时从链表头部取出一个页面
 * 4. 释放时将页面放回链表头部
 */

#include "../include/types.h"
#include "../include/memlayout.h"
#include "../include/riscv.h"
#include "../include/printf.h"

// ========================================
// 全局变量定义
// ========================================

// 空闲页面链表的头指针
// 这是一个全局变量，指向第一个空闲页面
static struct run *freelist = 0;

// 内存分配统计信息
static uint64 total_pages = 0;      // 总页面数
static uint64 free_pages = 0;       // 空闲页面数
static uint64 allocated_pages = 0;  // 已分配页面数

// ========================================
// 数据结构定义
// ========================================

// 空闲页面结构
// 每个空闲页面都包含一个指向下一个空闲页面的指针
struct run {
    struct run *next;  // 指向下一个空闲页面的指针
};

// ========================================
// 内部函数声明
// ========================================

// 初始化内存分配器
static void kinit(void);

// 将页面添加到空闲链表
static void add_to_freelist(void *pa);

// 从空闲链表移除页面
static void *remove_from_freelist(void);

// 检查地址是否有效
static int is_valid_address(uint64 pa);

// 内存设置函数
static void* kalloc_memset(void *dst, int c, uint64 n);

// ========================================
// 内部函数实现
// ========================================

/*
 * 初始化内存分配器
 * 扫描所有可用内存，将页面添加到空闲链表
 */
static void kinit(void)
{
    // 计算总页面数
    total_pages = (PHYSTOP - KERNBASE) / PGSIZE;
    free_pages = total_pages;
    allocated_pages = 0;
    
    printf("内存分配器初始化：\n");
    printf("  总内存：%d MB\n", (int)((PHYSTOP - KERNBASE) / (1024*1024)));
    printf("  总页面数：%d\n", (int)total_pages);
    printf("  页面大小：%d KB\n", PGSIZE / 1024);
    
    // 将内核代码区域标记为已使用
    // 这里简化处理，假设内核代码占用前1MB
    uint64 kernel_end = KERNBASE + 0x100000;  // 内核结束地址
    uint64 kernel_pages = (kernel_end - KERNBASE + PGSIZE - 1) / PGSIZE;
    
    printf("  内核占用页面：%d\n", (int)kernel_pages);
    
    // 将剩余页面添加到空闲链表
    for (uint64 pa = kernel_end; pa < PHYSTOP; pa += PGSIZE) {
        add_to_freelist((void*)pa);
    }
    
    // 更新空闲页面数（减去内核占用的页面）
    free_pages = total_pages - kernel_pages;
    allocated_pages = kernel_pages;
    
    printf("  可用页面数：%d\n", (int)free_pages);
    printf("内存分配器初始化完成！\n\n");
}

/*
 * 将页面添加到空闲链表头部
 * @param pa 物理地址
 */
static void add_to_freelist(void *pa)
{
    struct run *r = (struct run*)pa;
    r->next = freelist;
    freelist = r;
    free_pages++;
}

/*
 * 从空闲链表头部移除一个页面
 * @return 物理地址，如果没有空闲页面则返回0
 */
static void *remove_from_freelist(void)
{
    if (freelist == 0) {
        return 0;  // 没有空闲页面
    }
    
    struct run *r = freelist;
    freelist = r->next;
    free_pages--;
    allocated_pages++;
    
    return (void*)r;
}

/*
 * 检查物理地址是否有效
 * @param pa 物理地址
 * @return 1表示有效，0表示无效
 */
static int is_valid_address(uint64 pa)
{
    // 检查地址是否在有效范围内
    if (pa < KERNBASE || pa >= PHYSTOP) {
        return 0;
    }
    
    // 检查地址是否页对齐
    if ((pa & PGMASK) != 0) {
        return 0;
    }
    
    return 1;
}

// ========================================
// 公共接口函数
// ========================================

/*
 * 分配一个物理页面
 * @return 物理地址，如果分配失败返回0
 */
void* kalloc(void)
{
    void *pa = remove_from_freelist();
    
    if (pa == 0) {
        printf("错误：内存分配失败！没有可用的物理页面\n");
        return 0;
    }
    
    // 清零页面内容
    // 这是一个重要的安全措施，防止新分配的页面包含垃圾数据
    kalloc_memset(pa, 0, PGSIZE);
    
    return pa;
}

/*
 * 释放一个物理页面
 * @param pa 物理地址
 */
void kfree(void *pa)
{
    // 检查参数有效性
    if (pa == 0) {
        printf("错误：尝试释放空指针\n");
        return;
    }
    
    if (!is_valid_address((uint64)pa)) {
        printf("错误：无效的物理地址 0x%p\n", pa);
        return;
    }
    
    // 将页面添加到空闲链表
    add_to_freelist(pa);
    allocated_pages--;
}

/*
 * 获取内存分配统计信息
 * @param total 总页面数
 * @param free 空闲页面数
 * @param allocated 已分配页面数
 */
void kalloc_stats(uint64 *total, uint64 *free, uint64 *allocated)
{
    if (total) *total = total_pages;
    if (free) *free = free_pages;
    if (allocated) *allocated = allocated_pages;
}

/*
 * 打印内存分配统计信息
 */
void kalloc_print_stats(void)
{
    printf("=== 内存分配统计 ===\n");
    printf("总页面数：%d\n", (int)total_pages);
    printf("空闲页面数：%d\n", (int)free_pages);
    printf("已分配页面数：%d\n", (int)allocated_pages);
    // 使用整数计算避免浮点数问题
    uint64 usage_percent = (allocated_pages * 100) / total_pages;
    printf("内存使用率：%d%%\n", (int)usage_percent);
    printf("==================\n\n");
}

/*
 * 初始化内存分配器
 * 这个函数在系统启动时调用
 */
void kalloc_init(void)
{
    kinit();
}

/*
 * 简单的内存复制函数
 * @param dst 目标地址
 * @param src 源地址
 * @param n 字节数
 */
void* memcpy(void *dst, const void *src, uint64 n)
{
    char *d = (char*)dst;
    const char *s = (const char*)src;
    
    for (uint64 i = 0; i < n; i++) {
        d[i] = s[i];
    }
    
    return dst;
}

/*
 * 简单的内存设置函数
 * @param dst 目标地址
 * @param c 要设置的字节值
 * @param n 字节数
 */
void* kalloc_memset(void *dst, int c, uint64 n)
{
    char *d = (char*)dst;
    
    for (uint64 i = 0; i < n; i++) {
        d[i] = c;
    }
    
    return dst;
}
